/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/


#include "d3d_local.h"

/*-----------------------------------------------------------------------------
	Scene entity stuff :
-----------------------------------------------------------------------------*/

//
//	ERSEntity::ERSEntity
//
ERSEntity::ERSEntity( ERScene *scene, IPxTriMesh mesh, const EVec4 &pos, const EQuat &orient )
{
	this->scene			=	scene;
	this->position		=	pos;
	this->orient		=	orient;
	this->velocity		=	EVec4(0,0,0,0);
	this->work_mesh		=	mesh->Clone();
	this->origin_mesh	=	mesh;
	deleted				=	false;
	
	//	get rend mesh :
	this->mesh	=	scene->rs->mss->CreateEmptyModel();
	this->mesh->UpdateFromMesh( mesh );

	//	get	shading groups 
	//	and set appropriate materials :
	sgs.clear();
	for (uint i=0; i<mesh->GetSGNum(); i++) {
		EShadingGroup	sg = mesh->GetSG(i);
		
		ERShadingGroup	rsg;
		rsg.start	=	sg.start;
		rsg.num		=	sg.num;
		//rsg.texture	=	scene->rs->txm->RegisterTexture( sg.shader );
		//rsg.shader	=	scene->sshs->GetShader( sg.shader );
		
		sgs.push_back(rsg);
	}
}


//
//	ERSEntity::~ERSEntity
//
ERSEntity::~ERSEntity( void )
{

}


//
//	ERSEntity::SetPose
//
void ERSEntity::SetPose( const EVec4 &pos, const EQuat &orient )
{
	this->position	=	pos;
	this->orient	=	orient;
}


//
//	ERSEntity::GetPose
//
void ERSEntity::GetPose( EVec4 &position, EQuat &orient ) const
{
	position	=	this->position;
	orient		=	this->orient;
}


//
//	ERSEntity::UpdateEntity
//
void ERSEntity::UpdateEntity( const ERendEntityDesc_s *entity_desc )
{
	ASSERT(0);
}


//
//	ERSEntity::GetEntityDesc
//
const ERendEntityDesc_s	* ERSEntity::GetEntityDesc( void ) const
{
	return NULL;
}


//
//	ERSEntity::GetVertexBuffer
//
const IPxVertexBuffer ERSEntity::GetVertexBuffer( void ) const
{
	return mesh;
}


//
//	ERSEntity::Render
//
void ERSEntity::Render( void ) //const
{
	if (sgs.empty()) {
		//LOG_WARNING("Empty shading group list");
		return;
	}

	//	entity is to be deleted :	
	if (deleted) {
		return;
	}
	
	//	set world matrix :
	EMatrix4	T = Matrix4Translate( position.x, position.y, position.z );
	EMatrix4	R = QuatToMatrix( orient );
	EMatrix4	W = R * T;
	scene->sshs->SetWorldMatrix( W );
	
	// set joints information
	if (work_mesh->GetJointNum() != 0)
	{
		/*static float time = 0.0f;
		static float delta = 0.1f;
		time += delta;
		if (time > 38.0f)
		{
			time = 0.0f;
		}

		work_mesh->AnimateSkeleton( time );*/

		//scene->sshs->SetSkinningEnabled(true);
		//scene->sshs->SetJoints(work_mesh);
	}
	else
	{
		scene->sshs->SetSkinningEnabled(false);
	}
	//	draw subsets :	
	for (uint i=0; i<sgs.size(); i++) {
		ERShadingGroup	rsg = sgs[i];
		
		bool need_shade = rsg.shader->Setup();

		if (need_shade) {
			mesh->DrawTris( rsg.start, rsg.num );
		}
	}
}


//
//	ERSEntity::Animate
//
void ERSEntity::Animate( float time )
{
	IPxTriMesh anim_mesh = work_mesh->Clone();

	if ( work_mesh->GetJointNum() != 0 )
	{
		work_mesh->AnimateSkeleton( time );
		for ( uint i = 0; i < anim_mesh->GetVertexNum(); i++ )
		{
			EVertex vertex = work_mesh->GetVertex( i );
			EVec4	vert_final_pose( 0.0f, 0.0f, 0.0f, 0.0f );
		
			for ( uint j = 0; j < 4; j ++ )
			{
				EJoint *joint = work_mesh->GetJoint( vertex.joint_inds[j] );
				EVec4 weight_pos ( vertex.position.x - joint->bind_pos.x, vertex.position.y - joint->bind_pos.y, vertex.position.z - joint->bind_pos.z, 0.0f );

				weight_pos = QuatRotateVector( weight_pos, QuatConjugate(joint->bind_orient) );

				weight_pos.w = 0.0f;
				weight_pos = QuatRotateVector( weight_pos, joint->orient );

				vert_final_pose += ( joint->position + weight_pos )*vertex.joint_weights[j];
			}
			vertex.position.x = vert_final_pose.x;
			vertex.position.y = vert_final_pose.y;
			vertex.position.z = vert_final_pose.z;
			anim_mesh->SetVertex( i, vertex );
		}
	}

	mesh->UpdateFromMesh( anim_mesh );
}


//
//	ERSEntity::Animate
//
void ERSEntity::Animate( const IPxTriMeshFrame frame )
{
	ASSERT( work_mesh->GetVertexNum()==origin_mesh->GetVertexNum() );
	ASSERT( frame->GetJointNum() <= 256);
	
	EMatrix4	matrices[256];

	IPxTriMeshFrame bind_frame = origin_mesh->GetBindFrame();

	for (uint i=0; i<frame->GetJointNum(); i++) {
		EJoint2 jnt;
		EJoint2 joint;

		frame->GetJoint(i, jnt);
		bind_frame->GetJoint(i, joint);

		matrices[i] = Matrix4Translate( -joint.position) * QuatToMatrix(QuatConjugate(joint.orient)) * QuatToMatrix(jnt.orient) * Matrix4Translate( jnt.position[0], jnt.position[1], jnt.position[2] ); 
		
		//if (jnt.parent_id==0xFFFFFFFF) {
		//} else {
		//	uint p = jnt.parent_id;
		//	matrices[i] =  matrices[p] * matrices[i];
		//}
	}
	
	for (uint i=0; i<work_mesh->GetVertexNum(); i++) 
	{
		EVertex v		= origin_mesh->GetVertex(i);
		EVec3	fin_p	= EVec3(0,0,0);
		EVec3	fin_t	= EVec3(0,0,0);
		EVec3	fin_b	= EVec3(0,0,0);
		EVec3	fin_n	= EVec3(0,0,0);
		
		for (uint j=0; j<GE_MAX_JOINT_INDICES; j++) {
		
			uint	jnt_id	=	v.joint_inds[j];
			float	jnt_w	=	v.joint_weights[j];
			
			fin_p	=	fin_p	+	Matrix4Transform( v.position,	matrices[ jnt_id ], false ) * jnt_w;
			fin_t	=	fin_t	+	Matrix4Transform( v.tangent,	matrices[ jnt_id ], true ) * jnt_w;
			fin_b	=	fin_b	+	Matrix4Transform( v.binormal,	matrices[ jnt_id ], true ) * jnt_w;
			fin_n	=	fin_n	+	Matrix4Transform( v.normal,		matrices[ jnt_id ], true ) * jnt_w;
			
		}
		
		v.position	=	fin_p;
		v.tangent	=	fin_t;
		v.binormal	=	fin_b;
		v.normal	=	fin_n;
		
		work_mesh->SetVertex(i, v);
	}
	
	mesh->UpdateFromMesh( work_mesh );
}

//
//	ERSEntity::AddDecal
//
void ERSEntity::AddDecal( EVec3 position, EVec3 direction, float radius, EString material )
{
	EVertex v;
	EMatrix4	T = Matrix4Translate( this->position );
	EMatrix4	R = QuatToMatrix( orient );
	EMatrix4	W = R * T;
	EMatrix4	WI = Matrix4Inverse(W);
	EMatrix4	RI = Matrix4Inverse(R);
	position = Matrix4Transform(position, WI, false);
	direction= Matrix4Transform(direction, RI, true);
	IPxTriMesh temp_mesh = ge()->CreateTriMesh(work_mesh->GetFormat());
	if (work_mesh->GetDecal(position, direction, radius, temp_mesh, v)){
		EShadingGroup sg;
		sg.num = temp_mesh->GetTriangleNum();
		sg.shader = material;
		temp_mesh->AttachSG(sg);

		const float k = 100;
		EVec4 offs(v.normal.x / k, v.normal.y/k, v.normal.z/k, 1);
		offs = Matrix4Transform(offs, R);

		scene->AddEntity(temp_mesh, this->position + offs, this->orient);
	}
	return;
	if (work_mesh->RayIntersect(position, direction, v)){
		
		IPxTriMesh m = ge()->CreateTriMesh(work_mesh->GetFormat());
		uint n0,n1,n2,n3;
		EVertex temp = v;
		
		EVec3 normal = Vec3Normalize(v.normal);
		EVec3 side = Vec3Cross(normal, EVec3(0,1,0));
		side = Vec3Normalize(side);
		EVec3 up = Vec3Cross(normal, side);
		up = Vec3Normalize(up);
		side *= radius/2;
		up *= radius/2;

		temp.position = v.position + Vec3Add(up, side);
		temp.uv0 = EVec2(0,0);
		n0 = m->AddVertex(temp);

		temp.position = v.position + Vec3Add(up, -side);
		temp.uv0 = EVec2(1,0);
		n1 = m->AddVertex(temp);

		temp.position = v.position + Vec3Add(-up, -side);
		temp.uv0 = EVec2(1,1);
		n2 = m->AddVertex(temp);

		temp.position = v.position + Vec3Add(-up, side);
		temp.uv0 = EVec2(0,1);
		n3 = m->AddVertex(temp);

		m->AddTriangle(n0, n1, n2);
		m->AddTriangle(n2, n3, n0);
		
		EShadingGroup sg;
		sg.num = 2;
		sg.shader = material;
		m->AttachSG(sg);
		const float k = 10;
		EVec4 offs(normal.x / k, normal.y/k, normal.z/k, 1);

		scene->AddEntity(m, this->position + offs, this->orient);
	}
}